package util;

import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 实现分布式锁，需要避免同一个机器上的多线程互斥
 * 
 * @author xiafan68
 * 
 */
public class RedisSpinLock implements Lock {
	private static final String ONE = "1";
	private RedisCluster cluster;
	private IPartition part;
	private String key;
	private Lock localLock = new ReentrantLock();
	private ThreadLocal<Boolean> locked = new ThreadLocal<Boolean>();

	public RedisSpinLock(RedisCluster cluster_, String resource_) {
		cluster = cluster_;
		part = cluster_.getPartition();
		key = resource_ + ":lck";
	}

	@Override
	public void lock() {
		localLock.lock();
		try {
			String ret = cluster.getJedis(part.shard(key)).getSet(key, ONE);
			while (ret != null) {
				// spin
				ret = cluster.getJedis(part.shard(key)).getSet(key, ONE);
			}
			locked.set(true);
		} finally {
			localLock.unlock();
		}
	}

	@Override
	public void lockInterruptibly() throws InterruptedException {
		localLock.lockInterruptibly();
		try {
			String ret = cluster.getJedis(part.shard(key)).getSet(key, ONE);
			while (ret != null) {
				// spin
				ret = cluster.getJedis(part.shard(key)).getSet(key, ONE);
			}
			locked.set(true);
		} finally {
			localLock.unlock();
		}

	}

	@Override
	public Condition newCondition() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean tryLock() {
		if (localLock.tryLock()) {
			try {
				String ret = cluster.getJedis(part.shard(key)).getSet(key, ONE);
				if (ret == null || !ret.equals(ONE)) {
					locked.set(true);
					return true;
				}
			} finally {
				localLock.unlock();
			}
		}
		locked.set(false);
		return false;
	}

	private static final int step = 5; // ms

	@Override
	public boolean tryLock(long arg0, TimeUnit arg1)
			throws InterruptedException {
		if (localLock.tryLock(arg0, arg1)) {
			long start = System.currentTimeMillis();
			try {
				String ret = cluster.getJedis(part.shard(key)).getSet(key, ONE);
				int count = 0;
				locked.set(true);
				while (ret != null) {
					if (System.currentTimeMillis() - start > arg1
							.toMillis(arg0)) {
						locked.set(false);
						break;
					}
					if (++count * step > System.currentTimeMillis() - start) {
						Thread.sleep(arg1.toMillis(arg0));
					}
				}// 未抛异常抛出循环则一定获得了锁
			} catch (Exception ex) {
				locked.set(false);
			} finally {
				localLock.unlock();
			}
		} else {
			locked.set(false);
		}
		return locked.get();
	}

	@Override
	public void unlock() {
		if (locked.get() != null && locked.get()) {
			cluster.getJedis(part.shard(key)).del(key);
		}
	}

	public static void main(String[] args) {
		// 测试spinlock能够保证互斥
		RedisCluster cluster = new RedisCluster(new String[] { "127.0.0.1" },
				6379);
		RedisSpinLock lock = new RedisSpinLock(cluster, "test");
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
		}
		Random rand = new Random();
		for (int i = 0; i < 3; i++) {
			lock.lock();
			try {
				System.out.println("locked " + System.currentTimeMillis());
				Thread.sleep(Math.abs(rand.nextInt()) % 1000);
			} catch (InterruptedException e) {
			} finally {
				lock.unlock();
			}
		}
	}
}
